/*
 * File:    SoapInvoker.java
 * Created: 28-Dec-2007
 * Version: $Id$
 *
 * COPYRIGHT (C) 2007-2008, Bitgate Software, LLC.  All Rights Reserve.
 * Released under the Creative Commons License version 2.5
 *
 * software@bitgatesoftware.com
 */

package com.bitgate.util.soap;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;

import static com.bitgate.util.debug.Debug.debug;
import static com.bitgate.util.debug.Debug.isDebugEnabled;

/**
 * This class provides a way to invoke the SOAP method that was found from a master SOAP class that contained an
 * invokable method.  It automatically determines the objects to pass to the class, and returns the result(s) in
 * a static call.
 * 
 * @author kenji
 * @since 0.7.1
 */
public class SoapInvoker
{
	/**
	 * This function invokes a class for a result to send back to the requesting SOAP service.  This does the
	 * automatic conversion of the parameters sent to the function and takes in the resulting response, returning
	 * that in the end.
	 * 
	 * @param soapClass <code>Class&lt;?&gt;</code> containing the overall super class that contains the SOAP method being called.
	 * @param soapMethod <code>Method</code> to call.
	 * @param parameters <code>List&lt;String&gt;</code> containing the parameters to pass to the SOAP method.
	 * @return <code>Object</code> containing the result, which must be cast in order to be used.
	 * @throws SoapException on any errors, containing the original exception when thrown.
	 */
	public static Object invoke(Class<?> soapClass, Method soapMethod, List<String> parameters) throws SoapException
	{
		Object returnObject = null;
		Class<?> parameterTypes[] = soapMethod.getParameterTypes();
		Object passedParameters[] = new Object[parameterTypes.length];
		
		for(int i = 0; i < parameterTypes.length; i++) {
			// What a pain in the ass.  This has to be cast to the array of the type of storage mechanism that
			// the class object requires.  Therefore, we have to write special cases for each and every object type.
			//
			// Note: I could be REALLY truly devious here and handle all of this using a dynamic enumeration type,
			// but I'll refrain; that would be too hard to read.
			if (parameterTypes[i].isArray()) {
				if (parameterTypes[i].getName().contains("String")) {
					String[] stringArray = new String[parameters.size() - i];
					int position = 0;
					
					for(int x = i; x < parameters.size(); x++) {
						stringArray[position++] = parameters.get(x);
					}
	
					passedParameters[i] = stringArray;
				} else if (parameterTypes[i].getName().contains("Integer")) {
					Integer[] intArray = new Integer[parameters.size() - i];
					int position = 0;
					
					for(int x = i; x < parameters.size(); x++) {
						intArray[position++] = Integer.valueOf(parameters.get(x));
					}
	
					passedParameters[i] = intArray;
				} else if (parameterTypes[i].getName().contains("Float")) {
					Float[] floatArray = new Float[parameters.size() - i];
					int position = 0;
					
					for(int x = i; x < parameters.size(); x++) {
						floatArray[position++] = Float.valueOf(parameters.get(x));
					}
	
					passedParameters[i] = floatArray;
				} else if (parameterTypes[i].getName().contains("Double")) {
					Double[] doubleArray = new Double[parameters.size() - i];
					int position = 0;
					
					for(int x = i; x < parameters.size(); x++) {
						doubleArray[position++] = Double.valueOf(parameters.get(x));
					}
	
					passedParameters[i] = doubleArray;
				} else {
					// Okay, I have absolutely NO IDEA why this doesn't work.  Therefore, I am going to add in the code
					// that calling enumerations with arrays will not be invokable (yet).  I hope someone else can look
					// into this and give me some insight.  I've already wasted two days on this.  And I'm probably pretty
					// close.  (But, this class code was pretty tough to write in itself.)
					
					HashMap<String, Class<?>> classTable = new HashMap<String, Class<?>>();
					Class<?> classes[] = soapClass.getClasses();
					
					if (classes != null) {
						for(Class<?> className : classes) {
							if (className.isEnum()) {
								classTable.put(className.getSimpleName(), className);
							}
						}
					}
					
					Object[] stringArray = new Object[parameters.size() - i];
					int position = 0;
					String enumClassName = parameterTypes[i].getSimpleName();
					
					if (enumClassName.endsWith("[]")) {
						enumClassName = enumClassName.substring(0, enumClassName.length() - 2);
					}
					
					Class<?> classEntry = classTable.get(enumClassName);
					
					for(int x = i; x < parameters.size(); x++) {
						for(Object obj : classEntry.getEnumConstants()) {
							if (parameters.get(x).equals(obj.toString())) {
								stringArray[position] = obj;
							}
						}
						
						if (isDebugEnabled()) {
							debug("Enum: Setting position " + position + " class '" + stringArray[position].toString() + "'");
						}
						
						position++;
					}
	
					passedParameters[i] = stringArray;
				}
			} else if (parameterTypes[i].getName().contains("Integer")) {
				passedParameters[i] = Integer.valueOf(parameters.get(i));
			} else if (parameterTypes[i].getName().contains("String")) {
				passedParameters[i] = parameters.get(i);
			} else if (parameterTypes[i].getName().contains("Float")) {
				passedParameters[i] = Float.valueOf(parameters.get(i));
			} else if (parameterTypes[i].getName().contains("Double")) {
				passedParameters[i] = Double.valueOf(parameters.get(i));
			} else if (parameterTypes[i].isEnum()) {
				Object enums[] = parameterTypes[i].getEnumConstants();
				
				for(Object enumName : enums) {
					if (enumName.toString().contains(parameters.get(i))) {
						passedParameters[i] = enumName;
						
						if (isDebugEnabled()) {
							debug("Enumeration found at position " + i + ": " + enumName.toString());
						}
						
						break;
					}
				}
			}
		}
		
		try {
			returnObject = soapMethod.invoke(soapClass.newInstance(), passedParameters);
			
			if (isDebugEnabled()) {
				debug("Invocation for method '" + soapMethod.getName() + "' successful.  ReturnType='" +
					returnObject.getClass().getName() + "'");
			}
		} catch(Exception e) {
			if (isDebugEnabled()) {
				debug("Unable to invoke method: " + e.getClass().getName());
				debug("Parameters=" + parameters.toString() + " parameterTypes=" + parameterTypes + " PassedParameters=" +
					passedParameters.toString() + " Size=" + passedParameters.length);
			}
			
			throw new SoapException("Unable to invoke method '" + soapMethod.getName() + "': " + e.getClass().getName(), e);
		}
		
		return returnObject;
	}
}